package gateway.config.security.login;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Sets;
import common.config.jwt.JwtUtil;
import common.config.jwt.TokenInfo;
import common.model.dto.LoginToken;
import common.model.dto.ModifyPasswordToken;
import common.model.enums.EmailNotifyTypeEnum;
import common.model.enums.EmailValidEnum;
import common.model.po.SysAuthority;
import common.model.po.SysRole;
import common.model.po.SysUser;
import common.model.po.SysWebConfig;
import common.model.request.SendEmailRequest;
import common.service.CommonSysWebConfigService;
import common.service.RedissonCacheService;
import common.util.EmailTemplateUtil;
import common.util.EncryptionUtil;
import gateway.feign.SystemFeign;
import gateway.model.dto.SecurityUser;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RMap;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 顺序：3
 * 自定义认证处理
 *
 * @author 米泽鹏
 * @since 2021-07-31 4:49 下午
 */
@Slf4j
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

	private final UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
	@Resource
	private UserDetailsService userDetailsService;
	@Resource
	private CommonSysWebConfigService webConfigService;
	@Resource
	private RedissonCacheService redissonCacheService;
	@Resource
	private SystemFeign systemFeign;
	@Value("${satisfactory.domain}")
	private String domain;

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		// 获取前端表单中输入后返回的用户编码、密码
		String username = (String) authentication.getPrincipal();
		String password = (String) authentication.getCredentials();
		// 根据用户名查询用户
		SecurityUser securityUser = (SecurityUser) userDetailsService.loadUserByUsername(username);
		// 验证密码
		assert securityUser != null;
		SysUser user = securityUser.getUser();
		boolean isCorrectPassword = EncryptionUtil.isValidPassword(password, securityUser.getPassword(), user.getSalt());
		if (!isCorrectPassword) {
			throw new BadCredentialsException("密码错误：" + username);
		}
		// 校验账号状态
		userDetailsChecker.check(securityUser);
		// 从缓存中获取用户具有的角色
		RMap<Long, Set<Long>> roleIdCacheByUserId = redissonCacheService.getAllRoleIdCacheByUserId();
		Set<Long> roleIdSetInCache = roleIdCacheByUserId.get(user.getId());
		if (CollUtil.isEmpty(roleIdSetInCache)) {
			throw new BadCredentialsException("该用户未分配角色");
		}
		List<SysRole> sysRoleCache = redissonCacheService.getSysRoleCache();
		List<SysRole> sysRoleList = sysRoleCache.stream().filter(role -> roleIdSetInCache.contains(role.getId())).collect(Collectors.toList());
		securityUser.setRoleList(sysRoleList);
		// 获取用户具有的权限
		RMap<Long, Set<Long>> authIdByRoleIdMap = redissonCacheService.getAllAuthIdCacheByRoleId();
		Set<Long> authIdSet = Sets.newHashSet();
		for (Long roleId : roleIdSetInCache) {
			Set<Long> authIdSetInCurrentRole = authIdByRoleIdMap.get(roleId);
			if (CollUtil.isNotEmpty(authIdSetInCurrentRole)) {
				authIdSet.addAll(authIdSetInCurrentRole);
			}
		}
		if (authIdSet.isEmpty()) {
			securityUser.setAuthorityList(ListUtil.empty());
		} else {
			List<SysAuthority> authorityCache = redissonCacheService.getSysAuthorityCache();
			List<SysAuthority> authorityList = authorityCache.stream().filter(authority -> authIdSet.contains(authority.getId())).collect(Collectors.toList());
			securityUser.setAuthorityList(authorityList);
		}
		// 获取用户前端布局配置
		SysWebConfig layoutConfig = webConfigService.lambdaQuery().eq(SysWebConfig::getUserId, user.getId()).one();
		securityUser.setWebConfig(layoutConfig);
		// 创建token登录令牌 - 之后访问系统其它接口直接通过token认证用户权限
		LoginToken loginToken = new LoginToken(user.getId(), user.getUsername(), user.getNickname(), securityUser.getRoleList().stream().map(SysRole::getId).collect(Collectors.toSet()), securityUser.getAuthorityList().stream().map(SysAuthority::getId).collect(Collectors.toSet()));
		TokenInfo loginTokenInfo = JwtUtil.createLoginToken(loginToken);
		securityUser.setToken(loginTokenInfo.getToken());
		securityUser.setExpire(loginTokenInfo.getExpireDate());
		// 缓存token
		Long userId = securityUser.getUser().getId();
		redissonCacheService.cacheLoginTokenString(userId, loginTokenInfo.getToken(), loginTokenInfo.getExpireDate()).wasSuccessfulAndThen(result -> {
			Boolean newKeyFlag = result.getData();
			// 检测多端登录
			if (!newKeyFlag && CollUtil.contains(user.getEmailNotifyTypes(), EmailNotifyTypeEnum.PUSHED_OFF.getValue()) && StrUtil.isNotBlank(user.getEmail()) && EmailValidEnum.VALID.getValue().equals(user.getEmailValid())) {
				SendEmailRequest sendEmailRequest = new SendEmailRequest();
				sendEmailRequest.setToList(Collections.singletonList(user.getEmail()));
				sendEmailRequest.setSubject("多端登录【幸福工厂游戏工具网站】");
				// 生成修改密码的token
				ModifyPasswordToken modifyPasswordToken = new ModifyPasswordToken();
				modifyPasswordToken.setUserId(user.getId());
				TokenInfo modifyPasswordTokenInfo = JwtUtil.createModifyPasswordToken(modifyPasswordToken);
				redissonCacheService.cacheModifyPasswordTokenString(user.getId(), modifyPasswordTokenInfo.getToken(), modifyPasswordTokenInfo.getExpireDate()).throwExceptionIfFailed();
				sendEmailRequest.setText(EmailTemplateUtil.getPushedOffContent(domain, modifyPasswordTokenInfo.getToken(), user.getUsername(), user.getNickname()));
				sendEmailRequest.setHtml(true);
				systemFeign.sendEmail(sendEmailRequest);
			}
		});
		return new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
	}

	@Override
	public boolean supports(Class<?> aClass) {
		return true;
	}

}
